package com.yeskery.nut.scan.bean;

import com.yeskery.nut.annotation.bean.Autowired;
import com.yeskery.nut.annotation.bean.Bean;
import com.yeskery.nut.annotation.bean.Lazy;
import com.yeskery.nut.annotation.bean.Qualifier;
import com.yeskery.nut.bean.*;
import com.yeskery.nut.scan.BeanAnnotationScanMetadata;
import com.yeskery.nut.scan.BeanCreator;
import com.yeskery.nut.scan.ConfigurationBeanAnnotationScanMetadata;
import com.yeskery.nut.util.BeanUtils;
import com.yeskery.nut.util.ReflectUtils;
import com.yeskery.nut.util.StringUtils;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * Configuration 注解Bean创建类
 * @author sprout
 * @version 1.0
 * 2022-07-17 22:22
 */
public class ConfigurationBeanCreator implements BeanCreator {

    /** FactoryBean注解器 */
    private final FactoryBeanRegister factoryBeanRegister;

    /** 应用上下文 */
    private final ApplicationContext applicationContext;

    /** 所有Bean扫描元数据类型 */
    private final Collection<BeanAnnotationScanMetadata> collection;

    /** 已创建的Configuration Bean元数据缓存 */
    private final Set<ConfigurationBeanAnnotationScanMetadata> alreadyConfigurationBeanCreateCache = new HashSet<>();

    /**
     * 构建一个Configuration 注解Bean创建类
     * @param factoryBeanRegister FactoryBean注解器
     * @param applicationContext 应用上下文
     * @param collection 所有Bean扫描元数据类型
     */
    public ConfigurationBeanCreator(FactoryBeanRegister factoryBeanRegister, ApplicationContext applicationContext,
                                    Collection<BeanAnnotationScanMetadata> collection) {
        this.factoryBeanRegister = factoryBeanRegister;
        this.applicationContext = applicationContext;
        this.collection = collection;
    }


    @Override
    public synchronized void createBean(BeanAnnotationScanMetadata beanAnnotationScanMetadata) {
        if (beanAnnotationScanMetadata == null
                || beanAnnotationScanMetadata.getSource() != BeanAnnotationScanMetadata.Source.CONFIGURATION
                || !(beanAnnotationScanMetadata instanceof ConfigurationBeanAnnotationScanMetadata)
                || alreadyConfigurationBeanCreateCache.contains(beanAnnotationScanMetadata)) {
            return;
        }

        ConfigurationBeanAnnotationScanMetadata configurationBeanAnnotationMetadata
                = (ConfigurationBeanAnnotationScanMetadata) beanAnnotationScanMetadata;
        AnnotationApplicationContext annotationApplicationContext = (AnnotationApplicationContext) applicationContext;
        if (!annotationApplicationContext.isMatchConditionBean(configurationBeanAnnotationMetadata.getOriginalType(), collection)
                || !annotationApplicationContext.isMatchConditionBean(configurationBeanAnnotationMetadata.getMethod(), collection)) {
            return;
        }

        Method method = configurationBeanAnnotationMetadata.getMethod();
        Lazy lazy = method.getAnnotation(Lazy.class);
        if (lazy == null) {
            lazy = configurationBeanAnnotationMetadata.getOriginalType().getAnnotation(Lazy.class);
        }
        Object bean;
        if (lazy != null && lazy.value()) {
            BaseApplicationContext baseApplicationContext = (BaseApplicationContext) applicationContext;
            Class<?> returnType = method.getReturnType();
            if (returnType.isInterface()) {
                bean = baseApplicationContext.getProxyObjectContext().createProxyObject(returnType,
                        new LazyConfigurationBeanWrapInvocationHandler(this, configurationBeanAnnotationMetadata));
            } else {
                Constructor<?> constructor = ReflectUtils.getBeanFitConstructor(returnType);
                if (constructor.getParameterCount() == 0) {
                    bean = baseApplicationContext.getProxyObjectContext().createProxyObject(returnType,
                            new LazyConfigurationBeanWrapInvocationHandler(this, configurationBeanAnnotationMetadata));
                } else {
                    Object[] arguments = new Object[constructor.getParameterCount()];
                    bean = baseApplicationContext.getProxyObjectContext().createProxyObject(returnType, constructor.getParameterTypes(), arguments,
                            new LazyConfigurationBeanWrapInvocationHandler(this, configurationBeanAnnotationMetadata));
                }
            }
        } else {
            bean = doCreateBean(configurationBeanAnnotationMetadata, true);
        }

        if (bean != null) {
            factoryBeanRegister.registerBean(getBeanName(configurationBeanAnnotationMetadata), beanAnnotationScanMetadata.isPrimary(),
                    bean, configurationBeanAnnotationMetadata.getMethod().getReturnType());
            alreadyConfigurationBeanCreateCache.add(((ConfigurationBeanAnnotationScanMetadata) beanAnnotationScanMetadata));
        }
    }

    /**
     * 执行创建bean对象
     * @param configurationBeanAnnotationMetadata Configuration注解的Bean元数据
     * @param checked 是否需要检查已注册该bean
     * @return 创建好的bean对象
     */
    public Object doCreateBean(ConfigurationBeanAnnotationScanMetadata configurationBeanAnnotationMetadata, boolean checked) {
        Class<?> clazz = configurationBeanAnnotationMetadata.getOriginalType();
        Object instance;
        try {
            instance = ReflectUtils.getTargetByApplication(applicationContext, clazz);
        } catch (Exception e) {
            throw new BeanException("@Configuration Class [" + clazz.getName() + "] Instance Create Fail.", e);
        }

        Method method = configurationBeanAnnotationMetadata.getMethod();
        String beanName = getBeanName(configurationBeanAnnotationMetadata);
        if (checked && factoryBeanRegister.containBean(beanName)) {
            alreadyConfigurationBeanCreateCache.add(configurationBeanAnnotationMetadata);
            return null;
        }

        Object bean;
        if (method.getParameterCount() <= 0) {
            try {
                bean = method.invoke(instance);
            } catch (Exception e) {
                throw new BeanException("@Configuration Class [" + clazz.getName()
                        + "] @Bean Type [" + method.getReturnType().getName() + "] Name [" + beanName
                        + "] Bean Instance Create Fail.", e);
            }
        } else {
            Object[] parameters = getMethodParameters(method, collection);
            try {
                bean = method.invoke(instance, parameters);
            } catch (Exception e) {
                throw new BeanException("@Configuration Class [" + clazz.getName()
                        + "] @Bean Type [" + method.getReturnType().getName() + "] Name [" + beanName
                        + "] Bean Instance Create Fail.", e);
            }
        }
        return bean;
    }

    /**
     * 获取方法参数
     * @param method 方法对象
     * @param collection 所有Bean扫描元数据类型
     * @return 方法参数
     */
    private Object[] getMethodParameters(Method method, Collection<BeanAnnotationScanMetadata> collection) {
        Parameter[] parameters = method.getParameters();
        Object[] params = new Object[parameters.length];
        for (int i = 0; i < parameters.length; i++) {
            Parameter parameter = parameters[i];
            Autowired autowired = parameter.getAnnotation(Autowired.class);
            Qualifier qualifier = parameter.getAnnotation(Qualifier.class);
            String parameterBeanName = null;
            if (qualifier != null && !StringUtils.isEmpty(qualifier.value())) {
                parameterBeanName = qualifier.value();
            }
            final String beanName = parameterBeanName;

            Type parameterParameterizedType = parameter.getParameterizedType();
            Class<?> type = BeanUtils.getDependBeanType(parameterParameterizedType);
            try {
                Object value = BeanUtils.getDependBeanObject(applicationContext, parameterBeanName, parameterParameterizedType);
                int beanSize = BeanUtils.getDependCollectionBeanSize(value);
                if (beanSize == -1) {
                    params[i] = value;
                } else {
                    List<BeanAnnotationScanMetadata> beanCollection = collection.stream()
                            .filter(t -> StringUtils.isEmpty(beanName) || t.getName().equals(beanName))
                            .filter(t -> type.isAssignableFrom(t.getType()))
                            .collect(Collectors.toList());
                    int defineBeanSize = beanCollection.size();
                    if (beanSize == defineBeanSize) {
                        params[i] =  value;
                    } else {
                        params[i] = getRegisteredConfigurationBeanInstance(beanCollection, autowired, parameterBeanName, parameterParameterizedType);
                    }
                }
            } catch (NoSuchBeanException e) {
                List<BeanAnnotationScanMetadata> beanCollection = collection.stream()
                        .filter(t -> StringUtils.isEmpty(beanName) || t.getName().equals(beanName))
                        .filter(t -> type.isAssignableFrom(t.getType()))
                        .collect(Collectors.toList());

                if (beanCollection.isEmpty()) {
                    if (autowired != null && !autowired.required()) {
                        params[i] = null;
                    } else {
                        throw e;
                    }
                } else if (beanCollection.size() > 1 && !BeanUtils.isCollectionDependBean(parameterParameterizedType)) {
                    throw new BeanException("Multi Bean Class [" + type + "] "
                            + (StringUtils.isEmpty(beanName) ? "" : "Bean Name [" + beanName + "] ") + "Found.");
                } else {
                    params[i] = getRegisteredConfigurationBeanInstance(beanCollection, autowired, parameterBeanName, parameterParameterizedType);
                }
            }
        }
        return params;
    }

    /**
     * 获取注册后的Configuration Bean实例
     * @param beanCollection 要注册的bean元数据
     * @param autowired autowired实例
     * @param beanName bean名称
     * @param type bean类型
     * @return 注册后的Configuration Bean实例
     */
    private Object getRegisteredConfigurationBeanInstance(List<BeanAnnotationScanMetadata> beanCollection, Autowired autowired,
                                                          String beanName, Type type) {
        beanCollection.forEach(factoryBeanRegister::registerBean);
        try {
            return BeanUtils.getDependBeanObject(applicationContext, beanName, type);
        } catch (NoSuchBeanException e1) {
            if (autowired != null && !autowired.required()) {
                return null;
            } else {
                throw e1;
            }
        }
    }

    /**
     * 获取bean名称
     * @param configurationBeanAnnotationMetadata Configuration注解的Bean元数据
     * @return bean名称
     */
    private String getBeanName(ConfigurationBeanAnnotationScanMetadata configurationBeanAnnotationMetadata) {
        Method method = configurationBeanAnnotationMetadata.getMethod();
        Bean beanAnnotation = method.getAnnotation(Bean.class);
        return StringUtils.isEmpty(beanAnnotation.value()) ? method.getName() : beanAnnotation.value();
    }
}
